/*
 * Copyright 2012 Evgeny Shishkin
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.yzx.lifeassistants.view.widget.toast;

import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;
import static com.yzx.lifeassistants.view.widget.toast.AppMsg.LENGTH_STICKY;

import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.WeakHashMap;

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Application;
import android.app.Application.ActivityLifecycleCallbacks;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.LinearLayout;

import com.yzx.lifeassistants.base.BaseApplication;

/**
 * @author Evgeny Shishkin
 */
class MsgManager extends Handler implements Comparator<AppMsg> {

	private static final int MESSAGE_DISPLAY = 0xc2007;
	private static final int MESSAGE_ADD_VIEW = 0xc20074dd;
	private static final int MESSAGE_REMOVE = 0xc2007de1;

	private static WeakHashMap<Activity, MsgManager> sManagers;
	private static ReleaseCallbacks sReleaseCallbacks;

	private final Queue<AppMsg> msgQueue;
	private final Queue<AppMsg> stickyQueue;

	private MsgManager() {
		msgQueue = new PriorityQueue<AppMsg>(1, this);
		stickyQueue = new LinkedList<AppMsg>();
	}

	/**
	 * @return A {@link MsgManager} instance to be used for given
	 *         {@link android.app.Activity}.
	 */
	static synchronized MsgManager obtain(Activity activity) {
		if (sManagers == null) {
			sManagers = new WeakHashMap<Activity, MsgManager>(1);
		}
		MsgManager manager = sManagers.get(activity);
		if (manager == null) {
			manager = new MsgManager();
			ensureReleaseOnDestroy(activity);
			sManagers.put(activity, manager);
		}

		return manager;
	}

	static void ensureReleaseOnDestroy(Activity activity) {
		if (SDK_INT < ICE_CREAM_SANDWICH) {
			return;
		}
		if (sReleaseCallbacks == null) {
			sReleaseCallbacks = new ReleaseCallbacksIcs();
		}
		sReleaseCallbacks.register(activity.getApplication());
	}

	static synchronized void release(Activity activity) {
		if (sManagers != null) {
			final MsgManager manager = sManagers.remove(activity);
			if (manager != null) {
				manager.clearAllMsg();
			}
		}
	}

	static synchronized void clearAll() {
		if (sManagers != null) {
			final Iterator<MsgManager> iterator = sManagers.values().iterator();
			while (iterator.hasNext()) {
				final MsgManager manager = iterator.next();
				if (manager != null) {
					manager.clearAllMsg();
				}
				iterator.remove();
			}
			sManagers.clear();
		}
	}

	/**
	 * Inserts a {@link AppMsg} to be displayed.
	 * 
	 * @param appMsg
	 */
	void add(AppMsg appMsg) {
		msgQueue.add(appMsg);
		if (appMsg.mInAnimation == null) {
			appMsg.mInAnimation = AnimationUtils.loadAnimation(
					appMsg.getActivity(), android.R.anim.fade_in);
		}
		if (appMsg.mOutAnimation == null) {
			appMsg.mOutAnimation = AnimationUtils.loadAnimation(
					appMsg.getActivity(), android.R.anim.fade_out);
		}
		displayMsg();
	}

	/**
	 * Removes all {@link AppMsg} from the queue.
	 */
	void clearMsg(AppMsg appMsg) {
		if (msgQueue.contains(appMsg) || stickyQueue.contains(appMsg)) {
			// Avoid the message from being removed twice.
			removeMessages(MESSAGE_DISPLAY, appMsg);
			removeMessages(MESSAGE_ADD_VIEW, appMsg);
			removeMessages(MESSAGE_REMOVE, appMsg);
			msgQueue.remove(appMsg);
			stickyQueue.remove(appMsg);
			removeMsg(appMsg);
		}
	}

	/**
	 * Removes all {@link AppMsg} from the queue.
	 */
	void clearAllMsg() {
		removeMessages(MESSAGE_DISPLAY);
		removeMessages(MESSAGE_ADD_VIEW);
		removeMessages(MESSAGE_REMOVE);
		clearShowing();
		msgQueue.clear();
		stickyQueue.clear();
	}

	void clearShowing() {
		final Collection<AppMsg> showing = new HashSet<AppMsg>();
		obtainShowing(msgQueue, showing);
		obtainShowing(stickyQueue, showing);
		for (AppMsg msg : showing) {
			clearMsg(msg);
		}
	}

	static void obtainShowing(Collection<AppMsg> from,
			Collection<AppMsg> appendTo) {
		for (AppMsg msg : from) {
			if (msg.isShowing()) {
				appendTo.add(msg);
			}
		}
	}

	/**
	 * Displays the next {@link AppMsg} within the queue.
	 */
	private void displayMsg() {
		if (msgQueue.isEmpty()) {
			return;
		}
		// First peek whether the AppMsg is being displayed.
		final AppMsg appMsg = msgQueue.peek();
		final Message msg;
		if (!appMsg.isShowing()) {
			// Display the AppMsg
			msg = obtainMessage(MESSAGE_ADD_VIEW);
			msg.obj = appMsg;
			sendMessage(msg);
		} else if (appMsg.getDuration() != LENGTH_STICKY) {
			msg = obtainMessage(MESSAGE_DISPLAY);
			sendMessageDelayed(msg,
					appMsg.getDuration() + appMsg.mInAnimation.getDuration()
							+ appMsg.mOutAnimation.getDuration());
		}
	}

	/**
	 * Removes the {@link AppMsg}'s view after it's display duration.
	 * 
	 * @param appMsg
	 *            The {@link AppMsg} added to a {@link ViewGroup} and should be
	 *            removed.s
	 */
	private void removeMsg(final AppMsg appMsg) {
		clearMsg(appMsg);
		final View view = appMsg.getView();
		ViewGroup parent = ((ViewGroup) view.getParent());
		if (parent != null) {
			appMsg.mOutAnimation.setAnimationListener(new OutAnimationListener(
					appMsg));
			view.clearAnimation();
			view.startAnimation(appMsg.mOutAnimation);
		}

		Message msg = obtainMessage(MESSAGE_DISPLAY);
		sendMessage(msg);
	}

	private void addMsgToView(AppMsg appMsg) {
		View view = appMsg.getView();
		if (view.getParent() == null) { // Not added yet
			final ViewGroup targetParent = appMsg.getParent();
			final ViewGroup.LayoutParams params = appMsg.getLayoutParams();
			LinearLayout lLayout = new LinearLayout(
					BaseApplication.getContext());
			LinearLayout.LayoutParams p = new LinearLayout.LayoutParams(
					LinearLayout.LayoutParams.MATCH_PARENT,
					LinearLayout.LayoutParams.WRAP_CONTENT);
			p.setMargins(0, 120, 0, 0);
			lLayout.setLayoutParams(p);
			view.setLayoutParams(p);
			lLayout.addView(view, p);
			if (targetParent != null) {
				targetParent.addView(lLayout, params);
			} else {
				appMsg.getActivity().addContentView(lLayout, params);
			}
		}
		view.clearAnimation();
		view.startAnimation(appMsg.mInAnimation);
		if (view.getVisibility() != View.VISIBLE) {
			view.setVisibility(View.VISIBLE);
		}

		final int duration = appMsg.getDuration();
		if (duration != LENGTH_STICKY) {
			final Message msg = obtainMessage(MESSAGE_REMOVE);
			msg.obj = appMsg;
			sendMessageDelayed(msg, duration);
		} else { // We are sticky, we don't get removed just yet
			stickyQueue.add(msgQueue.poll());
		}
	}

	@Override
	public void handleMessage(Message msg) {
		final AppMsg appMsg;
		switch (msg.what) {
		case MESSAGE_DISPLAY:
			displayMsg();
			break;
		case MESSAGE_ADD_VIEW:
			appMsg = (AppMsg) msg.obj;
			addMsgToView(appMsg);
			break;
		case MESSAGE_REMOVE:
			appMsg = (AppMsg) msg.obj;
			removeMsg(appMsg);
			break;
		default:
			super.handleMessage(msg);
			break;
		}
	}

	@Override
	public int compare(AppMsg lhs, AppMsg rhs) {
		return inverseCompareInt(lhs.mPriority, rhs.mPriority);
	}

	static int inverseCompareInt(int lhs, int rhs) {
		return lhs < rhs ? 1 : (lhs == rhs ? 0 : -1);
	}

	private static class OutAnimationListener implements
			Animation.AnimationListener {

		private final AppMsg appMsg;

		private OutAnimationListener(AppMsg appMsg) {
			this.appMsg = appMsg;
		}

		@Override
		public void onAnimationStart(Animation animation) {

		}

		@Override
		public void onAnimationEnd(Animation animation) {
			final View view = appMsg.getView();
			if (appMsg.isFloating()) {
				final ViewGroup parent = ((ViewGroup) view.getParent());
				if (parent != null) {
					parent.post(new Runnable() { // One does not simply
													// removeView
						@Override
						public void run() {
							parent.removeView(view);
						}
					});
				}
			} else {
				view.setVisibility(View.GONE);
			}
		}

		@Override
		public void onAnimationRepeat(Animation animation) {

		}
	}

	interface ReleaseCallbacks {
		void register(Application application);
	}

	@TargetApi(ICE_CREAM_SANDWICH)
	static class ReleaseCallbacksIcs implements ActivityLifecycleCallbacks,
			ReleaseCallbacks {
		private WeakReference<Application> mLastApp;

		public void register(Application app) {
			if (mLastApp != null && mLastApp.get() == app) {
				return; // Already registered with this app
			} else {
				mLastApp = new WeakReference<Application>(app);
			}
			app.registerActivityLifecycleCallbacks(this);
		}

		@Override
		public void onActivityDestroyed(Activity activity) {
			release(activity);
		}

		@Override
		public void onActivityCreated(Activity activity,
				Bundle savedInstanceState) {
		}

		@Override
		public void onActivityStarted(Activity activity) {
		}

		@Override
		public void onActivityResumed(Activity activity) {
		}

		@Override
		public void onActivityPaused(Activity activity) {
		}

		@Override
		public void onActivityStopped(Activity activity) {
		}

		@Override
		public void onActivitySaveInstanceState(Activity activity,
				Bundle outState) {
		}
	}
}